package com.googlecode.YaccSyntaxTool.grammar;

import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.googlecode.YaccSyntaxTool.export.Exporter;
import com.googlecode.YaccSyntaxTool.export.NotSupportedExportKindException;
import com.googlecode.YaccSyntaxTool.export.ReferenceMap;

/**
 * This class represents the right side of grammar rule. (= all lines between two empty lines in the y.output file)
 *
 * @author goe
 *
 */
public class Expression {

	/**
	 * Logger
	 */
	private final static Logger sLog = Logger.getLogger(Expression.class.getName());

	private final List<Term> terms;

	public Expression() {
		super();

		terms = new ArrayList<Term>();
	}

	public Expression(String iTermStr, Set<String> excludes) {
		this();

		addTerm(iTermStr,excludes);
	}

	public void addTerm(String iTermStr, Set<String> excludes) {
		terms.add(new Term(iTermStr,excludes));
	}


	public boolean isEmpty() {
		if (terms.size() == 1
			&& terms.get(0).isEmpty()) {
			return true;
		} else {
			return false;
		}
	}

	public void addTerm(Term term) {
		terms.add(term);
	}

	/*
		public int getTermCnt() {
			return terms.size();
		}

		public Term getTerm(int i) {
			return terms.get(i);
		}
	*/


	/*
	 * EXPORT
	 */
	public void export(Exporter exporter) throws NotSupportedExportKindException {
		boolean first = true;

		switch (exporter.getKind()) {
		case TXT:
			for (Term term : terms) {
				if (!first) {
					exporter.append(" | ");
				}
				term.export(exporter);
				first = false;
			}
			break;
		case HTML:
			for (Term term : terms) {
				exporter.appendLineBreak();
				if (!first) {
					exporter.append("|&nbsp;");
				} else {
					exporter.append(":&nbsp;");
				}
				term.export(exporter);
				first = false;

			}
			break;
		case HTMLCOMPACT:
			for (Term term : terms) {
				if (!first) {
					exporter.append(" | ");
				}
				exporter.append("<nobr>");
				term.export(exporter);
				exporter.append("</nobr>");
				first = false;

			}
			break;
		default:
			throw new NotSupportedExportKindException(exporter.getKind());
		}
	}

	/*
	 * REFERENCES
	 */
	public void fillReferenceMap(ReferenceMap refMap, FactorType type, String refFrom) {

		for (Term term : terms) {
			term.fillReferenceMap(refMap,type,refFrom);
		}
	}

	/*
	 * SIMPLIFY
	 */

	// apply advantages of EBNF to expressions
	public void simplify() {


		optional();
	}

	// identify terms with optional factors and combine them
	private void optional() {
		Term termLong = null, termShort = null;
		int maxSimiliarty = -1;

		for (Term term : terms) {
			Term similiarTerm = findMostSimiliarTerm(term);
			if (similiarTerm != null) {
				// combine both by using optional factor(s)
				// TODO
				int similiarity = term.getSimilarity(similiarTerm);

				sLog.log(Level.FINE,"Similiarity: "+similiarity);
				sLog.log(Level.FINE,"TERM1: "+term);
				sLog.log(Level.FINE,"TERM2: "+similiarTerm);

				if (similiarity > maxSimiliarty) {
					maxSimiliarty = similiarity;
					if (term.getFactorCnt() > similiarTerm.getFactorCnt()) {
						termLong = term;
						termShort = similiarTerm;
					} else {
						termLong = similiarTerm;
						termShort = term;
					}
				}
			}
		}

		// combine the long the short term making factors in the long term optional
		if (termLong != null && termShort != null) {

			termLong.makeOptionalExpression(termShort);
			terms.remove(termShort);
		}
	}

	public boolean isRepeat(String nonterminal) {
		boolean rv = false;

		// there must be exactly two terms in the expression
		if (terms.size() == 2) {
			Term tS=null,tL=null;
			Term t1 = terms.get(0);
			Term t2 = terms.get(1);

			// determine the shorter and the longer term (one term must have exactly one factor more than the other)
			if (t1.getFactorCnt()+1 == t2.getFactorCnt()) {
				tS = t1;
				tL = t2;
			} else if (t1.getFactorCnt()-1 == t2.getFactorCnt()) {
				tS = t2;
				tL = t1;
			}

			if (tS != null && tL != null) {
				// remove the nonterminal it self from the term (the factor that is responsible for the repeat)
				tL.removeFactor(new Factor(FactorType.NONTERMINAL,nonterminal));

				if (tL.equals(tS)) {
					rv = true;
				}
			}
		}
		return rv;
	}

	private Term findMostSimiliarTerm(Term iTerm) {
		int similarity,maxSimilarity=-1;
		Term similiarTerm = null;

		for (Term term : terms) {
			if (term.equals(iTerm)) {
				// ignore the input term during search
				continue;
			}
			similarity = term.getSimilarity(iTerm);
			// the higher the better (= number of matching factor, ordering must be the same)
			if (similarity > maxSimilarity) {
				maxSimilarity = similarity;
				similiarTerm = term;
			}
		}
		return similiarTerm;
	}

	/*
	 * EXTRACT
	 */
	public Set<String> getUsedNonterminals() {
		Set<String> usedNonterminals = new LinkedHashSet<String>(); // maintain the insert ordering!
		for (Term term : terms) {
			term.fillUsedNonterminals(usedNonterminals);
		}
		return usedNonterminals;
	}
}
